package com.itheima.ssm.service.impl;

import com.itheima.ssm.dao.IUserDao;
import com.itheima.ssm.domain.Role;
import com.itheima.ssm.domain.UserInfo;
import com.itheima.ssm.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * 分析Role，Pemession，UserInfo表之间的关系
 * Role表主要是存放角色表，Pemession表主要存放的是不同用户的不同访问权限，UserInfo表
 * 是用户信息表主要是作用于登录操作是的对用户信息的封装
 * 1. Role和Pemession之间存在多对多的关系
 *      一个角色可能有多种访问权限，一个访问权限可能对应多个角色
 * 2. UserInfo和Role之间存在多对多的关系
 *      一个用户可能有多个角，一个角色可能对应多个用户(普通用户群体，vip用户群体，管理员群体)
 * 3. Pemession和UserInfo之间存在多对多关系
 *      一个用户有多种权限，一种权限对应多个用户
 * 所以上述三个表之间均为多对多的映射关系
 *
 * 分析使用Spring Security做权限的步骤
 * 1. 导入Spring Security框架需要的jar包坐标
 *
 * 2. 配置spring-security.xml配置文件
 *
 * 3. 在web.xml配置文件中配置相关的spring-security
 *
 * 下面我们主要针对于spring-security的数据库权限验证
 *
 */


// 指定使用那个Service来实现认证操作
@Service("userService")
@SuppressWarnings("all")
public class IUserServiceImpl implements IUserService {

    @Autowired
    private IUserDao iUserDao;

    // 注入spring-security提供的加密类
    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;


    /*@Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserInfo userInfo = null;
        try {
            userInfo = iUserDao.findByUsername(username);
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 注意：这里如果只是传入用户名和密码我们登录是任然会抛出异常,因为需要授权那些用户可以登录使用，否则会一直登录失败
        // 注意：在password的前面需要添加一个前缀{noop}和配置文件中对应，否则无法解析
        User user = new User(userInfo.getUsername(),"{noop}"+userInfo.getPassword(),getAuthority()); // spring sercurity框架提供的，集成了UserDeatail
        return user;
    }*/

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserInfo userInfo = null;
        try {
            /*
                userInfo实体Bean的作用就是封装用户信息，实现登陆验证操作
                该对象中存放着用户的用户名和密码信息，还有对应的角色信息
            */
            userInfo = iUserDao.findByUsername(username);
        } catch (Exception e) {
            e.printStackTrace();
        }
        /*
            Spring Security提供的一个User对象，主要适用于实际的登陆验证的，上面的userinfo只是封装登陆信息
            由User的源码可知User对象集成UserDetail接口，所以满足当前方法的返回值对象
            构造方法1
            public User(String username, String password,
			                Collection<? extends GrantedAuthority> authorities)   其余的默认值为true
            构造方法2
            public User(String username, String password, boolean enabled,
			                boolean accountNonExpired, boolean credentialsNonExpired,
			                boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities)

			注意其中的参数：boolean enabled，账户时候可用，如果不可用此时的用户登录一样也是失败的，所以这就是为什么我们需要在
			数据库中设置用户状态这个字段，如果当前用户状态为0，表明当前账户不可用，如果为1，表明当前账户可以使用，一旦不可用实际
			上就没有必要去继续验证用户信息了，所以我们给user对象传递参数的时候需要通过userinfo实体bean中的用户状态属性来给当前
			enabled属性赋值，三元运算即可实现 userInfo.getStatus() == 0 ? false : true
        */
        /*从userinfo对象中取出role集合放置到到getAuthority方法中，这样我们就可以从数据库中获取对应得我角色实行授权操作*/
        /*如果已经对密码进行加密我们就不需要添加前缀{noop}*/
        User user = new User(userInfo.getUsername(),userInfo.getPassword(),
                userInfo.getStatus() == 0 ? false : true,true,true,
                true,getAuthority(userInfo.getRoles()));

        return user;
    }/**/

    /**
     * 获取权限列表
     * @return
     */
    public List<SimpleGrantedAuthority> getAuthority(List<Role> roleList) {
        List<SimpleGrantedAuthority> authorityList = new ArrayList<>();
        /**
         * 此处存在代码缺陷，因为存在字符串的耦合，我们需要将我们的授权的角色存放到数据库中
         * 然后从数据库中取出数据，这设置
         * 由于我们将用户信息都封装到UserInfo Bean中，注意到UserInfo 实体Bean中我们设置了
         * List<Role>成员属性，这个可以帮助我们封装授权的角色，所以我们在IUserDao中修改之前的sql
         * 在查询的同时封装Role对象，使用@Resutl注解帮助实现
         */
        for (Role role : roleList) {
            /*如果存放的是mysql数据库，需要将小写转化为大写，因为数据库中存放的是大写，统一规范*/
            authorityList.add(new SimpleGrantedAuthority("ROLE" + role.getRoleName().toUpperCase()));
        }
        return authorityList;
    }

    @Override
    public List<UserInfo> findAll() throws Exception{
        return iUserDao.findAll();
    }

    /**
     * 添加用户信息到数据库中
     * @param userInfo
     * @throws Exception
     */
    @Override
    public void save(UserInfo userInfo) throws Exception {
        bCryptPasswordEncoder.encode(userInfo.getPassword());
        iUserDao.save(userInfo);
    }

    @Override
    public UserInfo findById(int userId) throws Exception {
        return iUserDao.findById(userId);
    }

    @Override
    public List<Role> findOtherRoles(int userId) throws Exception {
        return iUserDao.findOtherRoles(userId);
    }

    @Override
    public void addRoleToUser(int userId, String[] roleIds) throws Exception {
        for (String roleId : roleIds) {
            iUserDao.addRoleToUser(userId,roleId);
        }
    }
}
